package com.flyqiu.flow.core.impl;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.flyqiu.common.bean.FlyQiuBeanManage;
import com.flyqiu.common.tools.FlyQiuStreamTools;
import com.flyqiu.common.tools.StringTools;
import com.flyqiu.common.user.FlyQiuUser;
import com.flyqiu.flow.model.business.BaseFlyBusiness;
import com.flyqiu.flow.api.cache.BpmnModelCache;
import com.flyqiu.flow.api.context.FlyQiuFlowGlobalContext;
import com.flyqiu.flow.api.context.ProcInstContext;
import com.flyqiu.flow.api.service.FlowRuActivityService;
import com.flyqiu.flow.api.service.FlowRunningService;
import com.flyqiu.flow.api.service.FlowTaskService;
import com.flyqiu.flow.api.service.IdentityLinkService;
import com.flyqiu.flow.api.service.base.FlowCommentService;
import com.flyqiu.flow.api.service.base.FlowVariableService;
import com.flyqiu.flow.api.service.ru.FlowRuVariableService;
import com.flyqiu.flow.api.std.ElementCompleteHandler;
import com.flyqiu.flow.common.enums.IdentityLinkType;
import com.flyqiu.flow.common.enums.ProcTaskDelegateStatus;
import com.flyqiu.flow.common.enums.ProcTaskStatus;
import com.flyqiu.flow.common.excepli.TaskNotFoundException;
import com.flyqiu.flow.common.excepli.TaskSuspendException;
import com.flyqiu.flow.common.std.IProIcActivityMove;
import com.flyqiu.flow.core.DefaultProcInstContext;
import com.flyqiu.flow.core.impl.ru.FlowRuActivityServiceImpl;
import com.flyqiu.flow.core.var.VariableScope;
import com.flyqiu.flow.mapper.*;
import com.flyqiu.flow.bpmn.element.ProcessModel;
import com.flyqiu.flow.model.entity.Comment;
import com.flyqiu.flow.model.entity.base.Activity;
import com.flyqiu.flow.model.entity.base.FlowBaseDao;
import com.flyqiu.flow.model.entity.base.Task;
import com.flyqiu.flow.model.entity.base.Variable;
import com.flyqiu.flow.model.entity.hi.ProcHiActivity;
import com.flyqiu.flow.model.entity.hi.ProcHiUserTask;
import com.flyqiu.flow.model.entity.ru.ProcRuActivity;
import com.flyqiu.flow.model.entity.ru.ProcRuIdentityLink;
import com.flyqiu.flow.model.entity.ru.ProcRuInst;
import com.flyqiu.flow.model.entity.ru.ProcRuUserTask;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class FlowTaskServiceImpl implements FlowTaskService {

    protected final FlowCommentService flowCommentService;
    protected final RuIdentityLinkMapper ruIdentityLinkMapper;
    protected final FlyQiuFlowGlobalContext procGlobalContext;
    protected final FlyQiuBeanManage procBean;
    protected final FlowVariableService flowVariableService;
    protected final IdentityLinkService identityLinkService;

    protected final RuUserTaskInstMapper ruTaskMapper;
    protected final HiUserTaskInstMapper hiTaskMapper;
    protected final ProcRuActivityMapper ruActivityMapper;
    protected final ProcHiActivityMapper hiActivityMapper;

    public FlowTaskServiceImpl(FlyQiuFlowGlobalContext procGlobalContext, FlowCommentService flowCommentService, IdentityLinkService identityLinkService, FlowVariableService flowVariableService) {
        this.procGlobalContext = procGlobalContext;
        procBean = procGlobalContext.getBeanManage();
        this.flowVariableService = flowVariableService;
        this.identityLinkService = identityLinkService;
        this.flowCommentService = flowCommentService;
        ruIdentityLinkMapper = procBean.getMapper(RuIdentityLinkMapper.class);
        ruTaskMapper = procBean.getMapper(RuUserTaskInstMapper.class);
        hiTaskMapper = procBean.getMapper(HiUserTaskInstMapper.class);
        ruActivityMapper = procBean.getMapper(ProcRuActivityMapper.class);
        hiActivityMapper = procBean.getMapper(ProcHiActivityMapper.class);
    }


    /**
     * 分页查询任务实例
     *
     * @param page
     * @param wrapper
     * @return
     */
    @Override
    public Page<ProcRuUserTask> pageTask(Page<ProcRuUserTask> page, Wrapper<ProcRuUserTask> wrapper) {
        return null;
    }

    @Override
    public ProcRuUserTask selectOneTaskInst(Wrapper<ProcRuUserTask> wrapper) {
        return ruTaskMapper.selectOne(wrapper);
    }

    /**
     * 列表查询任务实例
     *
     * @param wrapper
     * @return
     */
    @Override
    public List<ProcRuUserTask> listTask(Wrapper<ProcRuUserTask> wrapper) {
        return ruTaskMapper.selectList(wrapper);
    }

    /**
     * 查询任务数量
     *
     * @param wrapper
     * @return
     */
    @Override
    public long countTask(Wrapper<ProcRuUserTask> wrapper) {
        return ruTaskMapper.selectCount(wrapper);
    }

    /**
     * 添加任务变量
     *
     * @param taskInstId
     * @param taskVariable
     */
    @Override
    public void addVariable(String taskInstId, JSONObject taskVariable) {
        if (taskVariable.isEmpty()) return;
        ProcRuUserTask task = getTaskById(taskInstId);
        if (task == null) {
            throw new RuntimeException("task is null by taskInstId:" + taskInstId);
        }
        flowVariableService.addVariables(task.getProcInstId(), task.getActId(), taskVariable);
    }

    @Override
    public void setBusiness(String taskInstId, BaseFlyBusiness business) {
        if (business == null) return;
        LambdaQueryWrapper<ProcRuUserTask> taskLambdaQueryWrapper = new LambdaQueryWrapper<>();
        taskLambdaQueryWrapper.eq(FlowBaseDao::getId, taskInstId);
        if (countTask(taskLambdaQueryWrapper) < 1) {
            throw new RuntimeException("task is null by taskInstId:" + taskInstId);
        }
        ProcRuUserTask updateTask = new ProcRuUserTask();
        updateTask.setTaskBusinessId(business.getBusinessId());
        updateTask.setTaskBusinessKey(business.getBusinessKey());
        updateTask.setTaskBusinessParams(business.getBusinessParams());
        updateTask.setId(taskInstId);
        ruTaskMapper.updateById(updateTask);
    }

    @Override
    public void deleteBusiness(String taskInstId) {
        LambdaQueryWrapper<ProcRuUserTask> taskLambdaQueryWrapper = new LambdaQueryWrapper<>();
        taskLambdaQueryWrapper.eq(FlowBaseDao::getId, taskInstId);
        if (countTask(taskLambdaQueryWrapper) < 1) {
            return;
        }
        LambdaUpdateWrapper<ProcRuUserTask> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Task::getTaskBusinessId, null);
        updateWrapper.set(Task::getTaskBusinessKey, null);
        updateWrapper.set(Task::getTaskBusinessParams, null);
        ProcRuUserTask procRuUserTask = new ProcRuUserTask();
        procRuUserTask.setId(taskInstId);
        ruTaskMapper.update(procRuUserTask, updateWrapper);
    }

    /**
     * 添加备注
     *
     * @param taskInstId 任务实例id
     * @param type       类型
     * @param msg        消息内容
     * @param extParams  扩展参数
     */
    @Override
    public void addComment(String taskInstId, String type, String msg, JSONObject extParams) {
        ProcRuUserTask taskInst = getTaskById(taskInstId);
        Comment comment = new Comment();
        comment.setAction("addComment");
        comment.setType(type);
        comment.setMessage(msg);
        comment.setProcInstId(taskInst.getProcInstId());
        comment.setActId(taskInst.getActId());
        comment.setActInstId(taskInstId);
        comment.setCreateTime(LocalDateTime.now());
        comment.setExtParams(extParams);
        comment.setTenantId(taskInst.getTenantId());
        flowCommentService.save(comment);
    }

    /**
     * 任务添加候选人
     *
     * @param taskInstId
     * @param candidateUsers
     */
    @Override
    public void addCandidateUser(String taskInstId, Set<? extends FlyQiuUser> candidateUsers) {
        ProcRuUserTask taskInst = getTaskById(taskInstId);
        candidateUsers = candidateUsers.stream().filter(e -> StringTools.isNotBlank(e.getUniqueKey())).filter(FlyQiuStreamTools.distinctByKey(FlyQiuUser::getUniqueKey)).collect(Collectors.toSet());
        if (candidateUsers.isEmpty()) return;
        identityLinkService.actAdd(taskInst.getProcInstId(), taskInst.getActId(), taskInst.getId(), IdentityLinkType.CANDIDATE.getValue(), candidateUsers);
    }

    /**
     * 删除候选人
     *
     * @param taskInstId
     * @param userUniqueKeys
     */
    @Override
    public void delCandidateUser(String taskInstId, Set<String> userUniqueKeys) {
        userUniqueKeys = userUniqueKeys.stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
        if (userUniqueKeys.isEmpty()) return;
        LambdaQueryWrapper<ProcRuIdentityLink> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProcRuIdentityLink::getActInstId, taskInstId);
        queryWrapper.in(ProcRuIdentityLink::getUserUniqueKey, userUniqueKeys);
        ruIdentityLinkMapper.delete(queryWrapper);
    }


    @Override
    public List<ProcRuIdentityLink> getIdentityLink(String taskInstId) {
        LambdaQueryWrapper<ProcRuIdentityLink> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ProcRuIdentityLink::getActInstId, taskInstId);
        return ruIdentityLinkMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public void deleteComment(String commentId) {
        flowCommentService.removeById(commentId);
    }

    @Override
    public void updateComment(String commentId, String type, String message) {
        Comment comment = flowCommentService.getById(commentId);
        if (comment == null) {
            throw new RuntimeException("could not find comment:" + commentId);
        }
        comment.setType(type);
        comment.setMessage(message);
        flowCommentService.updateById(comment);
    }


    @Override
    public List<Comment> listComment(Wrapper<Comment> wrapper) {
        return flowCommentService.list(wrapper);
    }

    @Override
    public ProcInstContext getRunTaskContext(String taskInstId) {
        ProcRuUserTask task = getTaskById(taskInstId);
        return getTaskContext(task);
    }

    private ProcInstContext getTaskContext(ProcRuUserTask task) {
        if (task == null) {
            throw new RuntimeException("task be not null ");
        }
        BpmnModelCache bpmnModelCache = procGlobalContext.getFlowModelSourceManage().getBpmModeler(task.getProcDeployDefId());
        FlowRunningService flowRunningService = procBean.getBean(FlowRunningService.class);
        FlowRuVariableService flowRuVariableService = procBean.getBean(FlowRuVariableService.class);
        ProcRuInst ruInst = flowRunningService.getProcInstByInstId(task.getProcInstId());

        ProcessModel process = bpmnModelCache.getProcess(ruInst.getProcDeployDefKey());
        DefaultProcInstContext procContext = new DefaultProcInstContext();
        procContext.setProcInst(ruInst);
        procContext.setGlobalContext(procGlobalContext);
        procContext.setFlyQiuBpmModeler(bpmnModelCache.bpmModeler());
        procContext.setCurProcessModel(process);
        procContext.setProcDeployDef(bpmnModelCache.deployDef());
        List<Variable> procRuVariables = flowVariableService.listRuVariable(task.getProcInstId(), task.getActId()).stream().map(e -> (Variable) e).toList();
        VariableScope taskVariableScope = new VariableScope(procContext.getGlobalContext().getFlowVarSerializeManage(), procRuVariables);
        List<Variable> procVariables = new ArrayList<>(flowRuVariableService.getProcVariables(ruInst.getId()));
        VariableScope procVariableScope = new VariableScope(procGlobalContext.getFlowVarSerializeManage(), procVariables);
        procVariableScope.append(taskVariableScope);
        procContext.setProcVariables(procVariableScope);
        return procContext;
    }

    @Override
    public void complete(String taskInstId) {
        ProcRuUserTask task = getTaskById(taskInstId);
        if (!ProcTaskStatus.ACTIVATE.getValue().equals(task.getStatus())) {
            throw new TaskSuspendException("This task is not active, taskInstId:" + taskInstId, taskInstId);
        }
        if (ProcTaskDelegateStatus.PENDING.getValue().equals(task.getDelegateStatus())) {
            throw new RuntimeException("This task delegateStatus is PENDING, taskInstId:" + taskInstId);
        }
        FlyQiuBeanManage procBean = procGlobalContext.getBeanManage();
        FlowRuActivityService activityService = procBean.getBean(FlowRuActivityServiceImpl.class);
        LambdaQueryWrapper<ProcRuActivity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProcRuActivity::getId, task.getActId());
        ProcRuActivity ruActivity = activityService.getOne(queryWrapper);
        ProcInstContext taskContext = getTaskContext(task);
        ElementCompleteHandler actInst = procGlobalContext.getFlowElementManage().createCompleteInst(ruActivity.getActType());
        procGlobalContext.getTransactionManage().apply(() -> {
            actInst.complete(taskContext, ruActivity);
        });

    }


    @Override
    public void setOwner(String taskInstId, FlyQiuUser ownerUser) {
        if (null == ownerUser) {
            throw new RuntimeException("ownerUser cannot be null ");
        }
        ProcRuUserTask userTask = getRuTaskAndChecked(taskInstId);

        ProcRuUserTask ruUserTask = new ProcRuUserTask();
        ruUserTask.setId(taskInstId);
        ProcRuUserTask.setTaskOwner(ruUserTask, ownerUser);


        ProcHiUserTask hiUserTask = new ProcHiUserTask();
        hiUserTask.setId(taskInstId);
        ProcRuUserTask.setTaskOwner(hiUserTask, ownerUser);


        ProcRuIdentityLink link = ProcRuIdentityLink.create(ownerUser, IdentityLinkType.OWNER.getValue(), userTask.getTenantId());
        link.setActId(userTask.getActId());
        link.setActInstId(userTask.getId());
        link.setProcInstId(userTask.getProcInstId());


        procGlobalContext.getTransactionManage().apply(() -> {
            identityLinkService.deleteRuIdentityLink(userTask.getActId(), ownerUser);
            identityLinkService.add(link);
            ruTaskMapper.updateById(ruUserTask);
            hiTaskMapper.updateById(hiUserTask);
        });


    }

    @Override
    public void setAssignee(String taskInstId, FlyQiuUser assigneeUser) {
        if (null == assigneeUser) {
            throw new RuntimeException("assignee cannot be null ");
        }
        ProcRuUserTask userTask = getRuTaskAndChecked(taskInstId);
        ProcRuUserTask ruUserTask = new ProcRuUserTask();
        ruUserTask.setId(taskInstId);
        ruUserTask.setActId(userTask.getActId());
        ProcRuUserTask.setTaskAssignee(ruUserTask, assigneeUser);


        ProcHiUserTask hiUserTask = new ProcHiUserTask();
        hiUserTask.setId(taskInstId);
        hiUserTask.setActId(userTask.getActId());
        ProcHiUserTask.setTaskAssignee(hiUserTask, assigneeUser);

        ProcRuActivity procRuActivity = ruActivityMapper.selectById(userTask.getActId());
        Activity.setActivityAssignee(procRuActivity, assigneeUser);

        ProcHiActivity procHiActivity = hiActivityMapper.selectById(userTask.getActId());
        Activity.setActivityAssignee(procHiActivity, assigneeUser);


        ProcRuIdentityLink link = ProcRuIdentityLink.create(assigneeUser, IdentityLinkType.ASSIGNEE.getValue(), userTask.getTenantId());
        link.setActId(userTask.getActId());
        link.setActInstId(userTask.getId());
        link.setProcInstId(userTask.getProcInstId());

        procGlobalContext.getTransactionManage().apply(() -> {
            identityLinkService.deleteRuIdentityLink(userTask.getActId(), assigneeUser);
            identityLinkService.add(link);
            ruTaskMapper.updateById(ruUserTask);
            hiTaskMapper.updateById(hiUserTask);
            ruActivityMapper.updateById(procRuActivity);
            hiActivityMapper.updateById(procHiActivity);
        });


    }

    private ProcRuUserTask getRuTaskAndChecked(String taskInstId) {
        ProcRuUserTask userTask = getTaskById(taskInstId);
        if (null == userTask) {
            throw new TaskNotFoundException("Could not find task by taskInstId：" + taskInstId, taskInstId);
        }
        return userTask;
    }

    @Override
    public void delegate(String taskInstId, FlyQiuUser delegateUser) {
        if (null == delegateUser) {
            throw new RuntimeException("delegateUser cannot be null ");
        }

        ProcRuUserTask userTask = getRuTaskAndChecked(taskInstId);

        ProcRuUserTask ruUserTask = new ProcRuUserTask();
        ruUserTask.setId(taskInstId);
        ProcRuUserTask.setTaskOwner(ruUserTask, delegateUser);
        ruUserTask.setDelegateStatus(ProcTaskDelegateStatus.PENDING.getValue());


        ProcHiUserTask hiUserTask = new ProcHiUserTask();
        hiUserTask.setId(taskInstId);
        hiUserTask.setTaskOwner(ruUserTask, delegateUser);


        ProcRuIdentityLink link = ProcRuIdentityLink.create(delegateUser, IdentityLinkType.OWNER.getValue(), userTask.getTenantId());
        link.setActId(userTask.getActId());
        link.setActInstId(userTask.getId());
        link.setProcInstId(userTask.getProcInstId());


        procGlobalContext.getTransactionManage().apply(() -> {
            identityLinkService.deleteRuIdentityLink(userTask.getActId(), delegateUser);
            identityLinkService.add(link);
            ruTaskMapper.updateById(ruUserTask);
            hiTaskMapper.updateById(hiUserTask);
        });


    }

    @Override
    public void resolve(String taskInstId) {
        ProcRuUserTask userTask = getRuTaskAndChecked(taskInstId);
        if (!ProcTaskStatus.ACTIVATE.getValue().equals(userTask.getStatus())) {
            throw new TaskSuspendException("This task is not active, taskInstId:" + taskInstId, taskInstId);
        }
        ProcRuUserTask ruUserTask = new ProcRuUserTask();
        ruUserTask.setId(taskInstId);
        ruUserTask.setDelegateStatus(ProcTaskDelegateStatus.RESOLVE.getValue());
        ruTaskMapper.updateById(ruUserTask);

    }

    @Override
    public List<ProcRuUserTask> taskMoveTo(String taskInstId, String targetDefKey) {
        ProcRuUserTask runUserTask = getTaskById(taskInstId);
        if (runUserTask == null) {
            throw new RuntimeException("Could not find ProcRuUserTask instance with taskInstId id: " + taskInstId);
        }
        List<ProcRuActivity> procRuActivities = procBean.getBean(FlowRunningService.class).activityMoveTo(runUserTask.getActId(), targetDefKey);
        Set<String> taskIds = procRuActivities.stream().map(Activity::getActInstId).filter(StringTools::isNotBlank).collect(Collectors.toSet());
        if (taskIds.isEmpty()) {
            return new ArrayList<>();
        }
        return ruTaskMapper.selectBatchIds(taskIds);
    }

    @Override
    public List<ProcRuUserTask> taskMoveTo(String taskInstId, String targetDefKey, IProIcActivityMove move) {
        ProcRuUserTask runUserTask = getTaskById(taskInstId);
        if (runUserTask == null) {
            throw new RuntimeException("Could not find ProcRuUserTask instance with taskInstId id: " + taskInstId);
        }

        List<ProcRuActivity> procRuActivities = procBean.getBean(FlowRunningService.class).activityMoveTo(runUserTask.getActId(), targetDefKey, move);
        Set<String> taskIds = procRuActivities.stream().map(Activity::getActInstId).filter(StringTools::isNotBlank).collect(Collectors.toSet());
        if (taskIds.isEmpty()) {
            return new ArrayList<>();
        }
        return ruTaskMapper.selectBatchIds(taskIds);
    }


}
